package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.MySocket.WebSocketServer;
import com.zzyl.base.PageResponse;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertDataPageQueryDto;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.service.AlertDataService;
import com.zzyl.utils.StringUtils;
import com.zzyl.vo.AlertDataVo;
import com.zzyl.vo.AlertRuleVo;
import com.zzyl.vo.DeviceDataVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AlertDataServiceImpl implements AlertDataService {

    @Autowired
    private AlertDataMapper alertDataMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Value("${zzyl.alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;

    @Value("${zzyl.alert.managerRole}")
    private String managerRole;

    @Override
    public PageResponse<AlertDataVo> pageQuery(AlertDataPageQueryDto alertDataPageQueryDto) {
        PageHelper.startPage(alertDataPageQueryDto.getPageNum(), alertDataPageQueryDto.getPageSize());

        LocalDateTime localDateTimeEnd = null;
        LocalDateTime localDateTimeStart = null;
        if (alertDataPageQueryDto.getStartTime() != null && alertDataPageQueryDto.getEndTime() != null) {
            localDateTimeEnd = LocalDateTimeUtil.of(alertDataPageQueryDto.getEndTime());
            localDateTimeStart = LocalDateTimeUtil.of(alertDataPageQueryDto.getStartTime());
        }

        Page<AlertDataVo> page = alertDataMapper.pageQuery(alertDataPageQueryDto.getId(), alertDataPageQueryDto.getDeviceName(),
                localDateTimeEnd, localDateTimeStart, alertDataPageQueryDto.getStatus());
        return PageResponse.of(page, AlertDataVo.class);
    }

    /**
     * 过滤数据
     *
     * @param deviceDataVo
     */
    @Override
    public void alertFilter(DeviceDataVo deviceDataVo) {
        LocalDateTime alarmTime = deviceDataVo.getAlarmTime();
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.SECONDS);
        if (between > 60) {
            return;
        }

        //查询所有产品的该产品下和该物模型下的规则
        List<AlertRule> allAlertRuleList = alertDataMapper.seleteByFunctionId(deviceDataVo.getFunctionId(), "-1", deviceDataVo.getProductKey());

        //查询该产品下 该设备下 该物模型下的规则
        List<AlertRule> theAllAlerRules = alertDataMapper.seleteByFunctionId(deviceDataVo.getFunctionId(), deviceDataVo.getIotId(), deviceDataVo.getProductKey());
        //合并
        Collection<AlertRule> alertRules = CollUtil.addAll(allAlertRuleList, theAllAlerRules);
        //如果为空则结束
        if (alertRules.isEmpty()) {
            return;
        }

        alertRules.forEach(alertRule -> deviceDateAlarmHandler(deviceDataVo, alertRule));

    }

    /**
     * 过滤数据是否触发报警规则
     *
     * @param deviceDataVo
     * @param alertRule
     */
    private void deviceDateAlarmHandler(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        //判断上报时间是否在规则的生效时间内
        String[] split = alertRule.getAlertEffectivePeriod().split("~");
        LocalTime startTime = LocalTime.parse(split[0]);
        LocalTime endTime = LocalTime.parse(split[1]);
        //获取上报时间
        LocalTime time = LocalDateTimeUtil.of(deviceDataVo.getAlarmTime()).toLocalTime();
        //不在上报时间之内
        if (time.isAfter(endTime) || time.isBefore(startTime)) {
            return;
        }
        //获取iotId
        String iotId = deviceDataVo.getIotId();
        String aggCount = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + iotId + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();
        String key = CacheConstants.ALERT_SILENT_PREFIX + iotId + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();

        //判断时间是否达到阈值
        //上报的数据 和 规则中的阈值进行对比
        int compare = NumberUtil.compare(Double.parseDouble(deviceDataVo.getDataValue()), alertRule.getValue());
        //判断操作符
        if (alertRule.getOperator().equals(">=") && compare >= 0 ||
                alertRule.getOperator().equals("<") && compare < 0) {
            log.info("当前设备上报的数据符合规则异常");
        } else {
            //正常数据
            redisTemplate.delete(aggCount);
            return;
        }


        //判断是否有沉默周期
        //前缀+iotId+funcationId+规则id
        String silentData = redisTemplate.opsForValue().get(key);
        if (StrUtil.isNotEmpty(silentData)) {
            return;
        }

        //处理的逻辑 持续周期的内容
        //获取值
        String aggData = redisTemplate.opsForValue().get(aggCount);
        int serializable = StringUtils.isEmpty(aggData) ? 1 : Integer.parseInt(aggData) + 1;

        //count是否与持续周期相等
        if (ObjectUtil.notEqual(serializable, alertRule.getDuration())) {
            //不相等 设置数据
            redisTemplate.opsForValue().set(aggCount, serializable + "");
            return;
        }

        //报警
        //清理redis中的报警数据
        redisTemplate.delete(aggCount);
        //存储报警的沉默周期（设置过期时间为：规则中的沉默周期值）
        redisTemplate.opsForValue().set(key, deviceDataVo.getDataValue(), alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);

        //批量 保存报警数据
        //判断报警类型

        List<Long> custumerIs = null;
        if (alertRule.getAlertDataType().equals(0)) {
            //老人的异常数据
            //找老人所关联的护理员
            if (deviceDataVo.getLocationType() == 0) {
                custumerIs = deviceMapper.selectNursingIdByIotIdWhereElder(iotId);
            } else if (deviceDataVo.getLocationType() == 1 && deviceDataVo.getLocationType() == 2) {
                custumerIs = deviceMapper.selectNursingIdByIotIdWhereBed(iotId);
            }
        } else {
            //设备异常数据
            custumerIs = userRoleMapper.selectUserIdByRoleName(deviceMaintainerRole);
        }

        //无论什么情况都要通知超级管理员
        List<Long> list = userRoleMapper.selectUserIdByRoleName(managerRole);
        List<Long> allIds = CollUtil.addAllIfNotContains(list, custumerIs);
        //去重
        List<Long> collect = allIds.stream().distinct().collect(Collectors.toList());
        insertAlertData(collect, alertRule, deviceDataVo);
    }

    /**
     * 保存报警数据
     *
     * @param allIds
     * @param alertRule
     * @param deviceDataVo
     */
    private WebSocketServer webSocketServer;

    private void insertAlertData(List<Long> allIds, AlertRule alertRule, DeviceDataVo deviceDataVo) {
        AlertData alertData = BeanUtil.toBean(deviceDataVo, AlertData.class);
        String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRule.getFunctionName(), alertRule.getOperator(), alertRule.getValue(), alertRule.getDuration());
        alertData.setAlertReason((alertReason));
        alertData.setStatus(0);
        alertData.setType(alertRule.getAlertDataType());

        List<AlertData> collect = allIds.stream().map(userId -> {
            AlertData data = BeanUtil.toBean(alertData, AlertData.class);
            data.setUserId(userId);
            data.setAlertRuleId(alertRule.getId());
            return data;
        }).collect(Collectors.toList());

        alertDataMapper.batchInsert(collect);

        // websocket通知：弹窗消息
        AlertNotifyDto alertNotifyDto = BeanUtil.toBean(alertData, AlertNotifyDto.class);
        alertNotifyDto.setFunctionName(alertRule.getFunctionName());
        alertNotifyDto.setAlertDataType(alertData.getType());
        alertNotifyDto.setNotifyType(1);
        //给指定消费者发送消息
        webSocketServer.sendMessageToConsumer(alertNotifyDto, allIds);
    }


}
